#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include <string.h>

#include "ctree.h"
#include "builtins.h"

#include "hash.h"

#define ALIAS_TABLE_SIZE 47 /* this should be large enough for a long time */

hash_table_t *alias_table;

#define BUILTIN(name)                               \
  if (cli_register_builtin(#name, do_##name) < 0) { \
    return -1;                                      \
  }

static ctree_leaf_t  builtins;/* double linked list for builtin commands */

static int do_exit(int argc, char **argv)
{
  (void) argc;
  (void) argv;

  assert(current);

  if (argc == 1) {
    current = current->father;
    /*
     * we don't need following code, since cli engine will leave mainloop
     * when current==NULL.
     *
     *    if (current == NULL) {
     *      exit(0);
     *    }
     */
  } else {
    fprintf(stderr,
	    "usage: %s\n"
	    "exit current command level.\n",
	    argv[0]);
  }

  return 0;
}

static int print_escstr(char *str)
{
  while(*str) {
    if (*str == '"') {
      putchar('\\');
    } 
    putchar(*str);
  }
  return 0;
}

static int do_alias(int argc, char **argv)
{
  int i;
  hash_t *hi;
  alias_t *ali;

  switch (argc) {
  case 1:  /* show all alias */
    for (i=0; i<ALIAS_TABLE_SIZE; i++) {
      for(hi=alias_table->table[i]; hi; hi=hi->next) {
	printf("alias \"");
	print_escstr(hi->key);
	printf("\" \"");
	print_escstr(((alias_t*)hi)->alias);
	printf("\"\n");
      }
    }
    return 0;
  case 2:  /* show desired alias */
    hi = hash_table_index(alias_table, argv[1]);
    if (hi) {
      printf("alias \"");
      print_escstr(hi->key);
      printf("\" \"");
      print_escstr(((alias_t*)hi)->alias);
      printf("\"\n");
      return 0;
    } else {
      printf("alias: %s: not found.\n", argv[1]);
      return -1;
    }
  case 3:  /* register new alias */
    ali = malloc(sizeof(alias_t));
    if (ali == NULL) {
      perror("malloc");
      return -1;
    }

    ali->hash_header.key = strdup(argv[1]);
    ali->alias = strdup(argv[2]);

    hash_table_add(alias_table, (hash_t*)ali);

    return 0;
  default: /* show help */
    fprintf(stderr,
	    "usage: %s                   show all exist alias\n"
	    "       %s <str> <cmd>       make <str> as alias of <cmd>.\n",
	    argv[0], argv[0]);
    return -1;
  }
}

static int do_unalias(int argc, char **argv)
{
  int i;
  alias_t *ali;

  if (argc == 1) {
    fprintf(stderr,
	    "usage: %s <alias> [alias] ...\n"
	    "unalias all those <alias>.\n",
	    argv[0]);
    return -1;
  }

  for (i=1; i<argc; i++) {
    ali = (alias_t*)hash_table_del(alias_table, argv[i]);
    if (ali) {
      free(ali->hash_header.key);
      free(ali->alias);
      free(ali);
    }
  }

  return 0;
}

/*
 * cli_register_builtin - add a builtin to builtin list
 *
 * @name   builtin command name
 * @action builtin command function callback
 *
 * return -1 on error, 0 on success.
 */
static int cli_register_builtin(char *name, int (*action)(int, char**))
{
  ctree_leaf_t cmd;

  /* sanity check first */
  if (builtins) {
    cmd = builtins;
    do {
      if (strcmp(name, cmd->name) == 0) {
	fprintf(stderr, "error: register an exist builtin: %s.\n", name);
	return -1;
      }
      cmd = cmd->rsibling;
    } while(cmd != builtins);
  }

  cmd = ctree_new_cmd(name, action);
  if (!cmd) {
    fprintf(stderr, "%s: failed to register this builtin command.\n", name);
    return -1;
  }

  if (builtins) {
    cmd->rsibling = builtins;
    cmd->lsibling = builtins->lsibling;
    cmd->rsibling->lsibling = cmd;
    cmd->lsibling->rsibling = cmd;
  } else {
    builtins = cmd->rsibling = cmd->lsibling = cmd;
  }

  return 0;
}
  

/*
 * cli_builtins_init - initialize all builtin commands
 *
 * return -1 on error, 0 on success.
 */
int cli_builtins_init(void)
{
  alias_table = init_hash_table(ALIAS_TABLE_SIZE);
  if (!alias_table) {
    return -1;
  }

  /* list all builtins here */
  BUILTIN(exit);
  BUILTIN(alias);
  BUILTIN(unalias);

  return 0;
}

/*
 * cli_builitins_fini - deinitialize all builtin commands
 */
void cli_builtins_fini(void)
{
  ctree_leaf_t node1, node2;
  
  if (!builtins) {
    return;
  }

  for (node1=builtins, node2=node1->rsibling;
       node2 != builtins;
       node1=node2, node2=node1->rsibling) {
    free(node1);
  }

  free(node1);

  return;
}

/*
 * cli_builtins_run - exec desired builtin command
 *
 * @argc   arguments count of command
 * @argv   arguments list of command
 *
 * return command return value, or if no such comman exist, return -1
 */
int cli_builtins_run(int argc, char **argv)
{
  ctree_leaf_t node;

  /* sanity check first */
  if ((argc < 1) || (argv == NULL)) {
    return -1;
  }

  node = builtins;
  do {
    if (strcmp(node->name, argv[0]) == 0) {
      return node->action(argc, argv);
    }
    node = node->rsibling;
  } while (node != builtins);

  fprintf(stderr, "%s: no such command.\n", argv[0]);
  return -1;
}
  
